Skip to content

Map constructor keeps a repeated key's last value; a looping chain throws - #101

Merged
ASDAlexander77 merged 1 commit into
mainfrom
map-set-loose-ends
Nov 14, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
map-set-loose-ends

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

The remaining known loose ends in Map and Set, in one PR.

What

  • new Map with a repeated key keeps the last value. new Map([["a", 1], ["b", 2], ["a", 3]]).get("a") was 1; it is 3 now, and the key keeps its first place, as in JavaScript.
  • A chain of entries that goes round throws an Error. Six places detect it and only had a comment saying "throw exception".

Why

  • The Map constructor inserted with InsertionBehavior.ThrowOnExisting. That branch was an empty stub, so a repeated key was silently skipped. It inserts with OverwriteExisting now.
  • A looping chain can only come from another thread changing the collection while this one reads it. In the two insert loops (Map.tryInsert, Set.AddIfNotPresent) the check had an empty body, so the loop would never have ended. The lookups and removals returned "not found".

Change

  • src/generics/lib.generics.ts:
    • InsertionBehavior.ThrowOnExisting is removed; nothing else needed it. Set's constructor and add passed it and now pass None. Their behaviour is unchanged: an equal value was already left alone.
    • The six loop checks throw new Error("Map or Set changed by another thread while in use").
  • tests/map_constructor_duplicates.ts (new): repeated keys in the Map constructor, with string and number keys, and repeated values in the Set constructor.

For the reviewer

  • The throws are not covered by a test: I found no way to make a chain loop from a single thread.
  • InsertionBehavior is not declared in lib.d.ts, so no declaration changes.

Testing

🤖 Generated with Claude Code

…rows

new Map([["a", 1], ["a", 3]]).get("a") was 1: the constructor inserted with
ThrowOnExisting, whose branch was an empty stub that skipped the entry. It
inserts with OverwriteExisting now, so the last value wins and the key keeps
its first place, as in JavaScript.

ThrowOnExisting is gone: nothing else needed it. Set's constructor and add
passed it too, and for them an equal value was already left alone; they pass
None.

The six places that find a chain of entries going round (a Map or a Set
changed by another thread while this one reads it) were comments saying
"throw exception". Two of them, in the insert loops, did nothing at all, so
the loop would not have ended. They throw an Error now.

map_constructor_duplicates.ts covers the constructors.

Tested in release on Windows x64 for gc, rc, none and own, compiled and under
the JIT; debug, Linux, x86 and wasm were not built locally.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 6806d40 into main Nov 14, 2026
9 checks passed
@ASDAlexander77
ASDAlexander77 deleted the map-set-loose-ends branch November 14, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant